Global Acceleratorで作成されるセキュリティグループをALBのセキュリティグループで許可するカスタムリソース作ってみた

Global Acceleratorで作成されるセキュリティグループをALBのセキュリティグループで許可するカスタムリソース作ってみた

Clock Icon2022.08.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。

今回は、Global Acceleratorエンドポイント作成時にAWS側で作成されるGlobal Accelerator用のセキュリティグループをALBのセキュリティグループに紐づけるカスタムリソースを作ってみました。

なぜそんなことをしているのか

Global Acceleratorエンドポイントグループ作成時、Global Acceleratorはエンドポイントが存在するVPCごとに1つずつセキュリティグループを作成します。

つまり、セキュリティグループを作成するのはCloudFormationではなく、Global Accelerator側となります。

そのため、Global AcceleratorエンドポイントとALBを同時に作成するテンプレートの場合、CloudFormation側でGlobal Acceleratorのセキュリティグループを定義していないので、ALBのセキュリティグループでGlobal Acceleratorのセキュリティグループを許可するルールが作成できない事象が発生します。

もちろんですが、Global Acceleratorエンドポイントグループを見ても、返り値としてセキュリティグループを取得することができませんでした。

AWS::GlobalAccelerator::EndpointGroup

今回は、CloudFormationテンプレートを利用して1発で設定する方法が2つあったのでご紹介します。

方法1(非推奨)

方法1は、Global Acceleratorエンドポイントグループ作成前に、セキュリティグループを作ってしまう方法です。

Global Acceleratorで作成されるセキュリティグループの名前は、私が検証した限り「GlobalAccelerator」と名前がついたセキュリティグループが対象VPCに作成されていました。

そのため、次のようにGlobal Acceleratorエンドポイントグループ作成前にセキュリティグループを作成します。

template.yaml
Resources:
  AlbSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VpcId
      GroupName: !Sub "${PrjPrefix}-alb-sg"
      GroupDescription: !Sub "${PrjPrefix}-alb-sg"
      Tags:
        - Key: Name
          Value: !Sub "${PrjPrefix}-alb-sg"
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      Name: !Sub "${PrjPrefix}-alb"
      Type: application
      IpAddressType: ipv4
      Scheme: internet-facing
      SecurityGroups:
        - !GetAtt AlbSg.GroupId
      Subnets: !Ref SubnetIds
      Tags:
        - Key: Name
          Value: !Sub "${PrjPrefix}-alb"

  # Global Accelerator用のSGを先に作る
  GlobalAcceleratorSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VpcId
      GroupName: "GlobalAccelerator" # 名前は固定
      GroupDescription: !Sub "${PrjPrefix}-ga-sg"
      Tags:
        - Key: Name
          Value: !Sub "${PrjPrefix}-ga-sg"

  AlbFromGaIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties: 
      GroupId: !GetAtt AlbSg.GroupId
      Description: GlobalAccelerator Communication
      FromPort: 80
      ToPort: 80
      IpProtocol: tcp
      SourceSecurityGroupId: !GetAtt GlobalAcceleratorSg.GroupId

  AcceleratorListeneralb:
    Type: AWS::GlobalAccelerator::EndpointGroup
    Properties:
      EndpointGroupRegion:
        Ref: AWS::Region
      ListenerArn:
        Fn::GetAtt:
          - AcceleratorListener
          - ListenerArn
      EndpointConfigurations:
        - EndpointId:
            Ref: Alb
    DependsOn:
      - GlobalAcceleratorSg # 明示的に依存関係を持たせる

ただし、以下のドキュメントにある通り、Global Acceleratorが作成するセキュリティグループを操作するのは非推奨とされています。

グローバルアクセラレータによって作成されたセキュリティグループ
グローバルアクセラレータとセキュリティグループを使用する場合は、次の情報とベストプラクティスを確認してください。

  • Global Accelerator は、Elastic Network インターフェイスに関連付けられているセキュリティグループを作成します。システムによって禁止されるわけではありませんが、これらのグループのセキュリティグループ設定を編集しないでください。
  • グローバルアクセラレータは、作成したセキュリティグループを削除しません。ただし、Global Accelerator では、アカウントのアクセラレータのエンドポイントでelastic network interface が使用されていない場合、エラスティックネットワークインターフェイスは削除されます。
  • Global Accelerator によって作成されたセキュリティグループは、保守する他のセキュリティグループのソースグループとして使用できますが、Global Accelerator は VPC で指定したターゲットにのみトラフィックを転送します。
  • Global Accelerator で作成されたセキュリティグループのルールを変更すると、エンドポイントが異常になることがあります。その場合は、AWS サポート詳細については、
  • グローバルアクセラレータは、VPC ごとに特定のセキュリティグループを作成します。特定の VPC 内のエンドポイント用に作成された Elastic ネットワークインターフェイスは、elastic network interface が関連付けられているサブネットに関係なく、すべて同じセキュリティグループを使用します。

クライアント IP アドレスの保存に関するベストプラクティス

方法2(オススメ)

というわけで、Global Acceleratorが作成するセキュリティグループを操作せずに設定する方法が方法2になります。

具体的には、Global Acceleratorが作成したセキュリティグループIDを取得してALBに許可するカスタムリソースになります。

以下のようにカスタムリソースとして設定することで、Global Acceleratorの非推奨項目を回避しつつALBに設定ができました。

template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Terraform pipeline template.
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
        - PrjPrefix
      - Label:
          default: "Network Configration"
        Parameters:
        - VpcId
        - SubnetIds
Parameters:
  PrjPrefix:
    Type: String
  VpcId:
    Type: AWS::EC2::VPC::Id
  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
Resources:
# ALB
  AlbSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VpcId
      GroupName: !Sub "${PrjPrefix}-alb-sg"
      GroupDescription: !Sub "${PrjPrefix}-alb-sg"
      Tags:
        - Key: Name
          Value: !Sub "${PrjPrefix}-alb-sg"
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      Name: !Sub "${PrjPrefix}-alb"
      Type: application
      IpAddressType: ipv4
      Scheme: internet-facing
      SecurityGroups:
        - !GetAtt AlbSg.GroupId
      Subnets: !Ref SubnetIds
      Tags:
        - Key: Name
          Value: !Sub "${PrjPrefix}-alb"
  AlbTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: /healthcheck
      HealthCheckPort: 80
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 6
      HealthyThresholdCount: 3
      Name: !Sub "${PrjPrefix}-alb"
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 3
      TargetType: ip
      VpcId: !Ref VpcId
  AlbListerner:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref AlbTargetGroup
      LoadBalancerArn: !Ref Alb
      Port: 80
      Protocol: HTTP

# GlobalAccelerator
  Accelerator:
    Type: AWS::GlobalAccelerator::Accelerator
    Properties:
      Name: !Sub "${PrjPrefix}-ga"
      Enabled: true
  AcceleratorListener:
    Type: AWS::GlobalAccelerator::Listener
    Properties:
      AcceleratorArn:
        Fn::GetAtt:
          - Accelerator
          - AcceleratorArn
      PortRanges:
        - FromPort: 80
          ToPort: 80
      Protocol: TCP
      ClientAffinity: NONE
  AcceleratorListenerAlb:
    Type: AWS::GlobalAccelerator::EndpointGroup
    Properties:
      EndpointGroupRegion:
        Ref: AWS::Region
      ListenerArn:
        Fn::GetAtt:
          - AcceleratorListener
          - ListenerArn
      EndpointConfigurations:
        - EndpointId:
            Ref: Alb

# Security Group
  ModifySecrityGroupRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
        - "arn:aws:iam::aws:policy/job-function/NetworkAdministrator"
      RoleName: !Sub "${PrjPrefix}-modify-security-group"

  ModifySecrityGroupLog:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/${PrjPrefix}-modify-security-group"

  ModifySecrityGroupFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "${PrjPrefix}-modify-security-group"
      Code:
        ZipFile: |
          import json
          import boto3
          import cfnresponse
          def handler(event, context):
              vpc_id = event['ResourceProperties']['VpcId']
              alb_sg_id = event['ResourceProperties']['AlbSgId']
              alb_lintener_port = int(event['ResourceProperties']['AlbListernerPort'])

              try:
                  if event['RequestType'] == 'Create':
                      ec2 = boto3.client('ec2')
                      globalaccelerator_sg_id = ec2.describe_security_groups(
                          Filters=[
                              {
                                  'Name': 'vpc-id',
                                  'Values': [ vpc_id ]
                              },
                              {
                                  'Name': 'group-name',
                                  'Values': [ 'GlobalAccelerator' ]
                              }
                          ]
                      )['SecurityGroups'][0]['GroupId']
                      response = ec2.authorize_security_group_ingress(
                          GroupId = alb_sg_id,
                          IpPermissions = [
                              {
                                  'FromPort': alb_lintener_port,
                                  'ToPort': alb_lintener_port,
                                  'IpProtocol': 'tcp',
                                  'UserIdGroupPairs': [
                                      {
                                          'GroupId': globalaccelerator_sg_id,
                                          'Description': 'GlobalAccelerator Communication'
                                      }
                                  ]
                              }
                          ]
                      )
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, response)
                  if event['RequestType'] == 'Delete':
                      ec2 = boto3.client('ec2')
                      globalaccelerator_sg_id = ec2.describe_security_groups(
                          Filters=[
                              {
                                  'Name': 'vpc-id',
                                  'Values': [ vpc_id ]
                              },
                              {
                                  'Name': 'group-name',
                                  'Values': [ 'GlobalAccelerator' ]
                              }
                          ]
                      )['SecurityGroups'][0]['GroupId']
                      response = ec2.revoke_security_group_ingress(
                          GroupId = alb_sg_id,
                          IpPermissions  = [
                              {
                                  'FromPort': alb_lintener_port,
                                  'ToPort': alb_lintener_port,
                                  'IpProtocol': 'tcp',
                                  'UserIdGroupPairs': [
                                      {
                                          'GroupId': globalaccelerator_sg_id,
                                          'Description': 'GlobalAccelerator Communication'
                                      }
                                  ]
                              }
                          ]
                      )
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'})
                  if event['RequestType'] == 'Update':
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'})
              except Exception as e:
                  print(e)
                  cfnresponse.send(event, context, cfnresponse.FAILED, {})
      Handler: index.handler
      MemorySize: 128
      Role: !GetAtt ModifySecrityGroupRole.Arn
      Runtime: "python3.9"
      Timeout: 60
      Tags:
        - Key: "Name"
          Value: !Sub "${PrjPrefix}-modify-security-group"
    DependsOn: ModifySecrityGroupLog

  ModifySecrityGroup:
    Type: Custom::ModifySecrityGroup
    Properties:
      ServiceToken: !GetAtt ModifySecrityGroupFunction.Arn
      VpcId: !Ref VpcId
      AlbSgId: !GetAtt AlbSg.GroupId
      AlbListernerPort: 80
    DependsOn:
      - AcceleratorListenerAlb

まとめ

以上、「Global AcceleratorのセキュリティグループをALBのセキュリティグループで許可するカスタムリソース作ってみた」でした。

Global Accelerator利用時に参考になるととても嬉しいです。

以上、AWS事業本部コンサルティング部のたかくに(@takakuni_)でした!

この記事をシェアする

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.